﻿using System;
using System.Collections.Generic;
using SoftwareConsulting.BI11.Studio.InternalServices;

namespace SoftwareConsulting.BI11.NumericalMethods.Classification
{    
    /// <summary>
    /// Класс абстрактной классификации
    /// </summary>
    public abstract class AbstractClassificator: IClassificator
    {
        private static int _attributesCount = 0;//число признаков

        /// <summary>
        /// Возвращает/устанавливает число признаков
        /// </summary>
        protected static int AttributesCount
        {
            get
            {
                return _attributesCount;
            }
            set
            {
                _attributesCount = value;
            }
        }

        /// <summary>
        /// Структура объекта классификации
        /// </summary>
        protected struct ClassificationObject
        {
            private double[] _attributes;//значения признаков (порядок имеет значение! считается что все признаки уже разбиты и отсортированы по группам длинной _groupsLengths)
            private int _class;//класс

            /// <summary>
            /// Конструктор структуры
            /// </summary>
            /// <param name="attributes"></param>
            /// <param name="class_"></param>
            public ClassificationObject(double[] attributes, int class_)
            {
                Assert.FailIfNot(attributes.Length == _attributesCount, Assert.Id.Assert00297);
                _attributes = attributes;
                _class = class_;
            }

            /// <summary>
            /// Возвращает значение признака с указанным индексом
            /// </summary>
            /// <param name="index"></param>
            /// <returns></returns>
            public double GetAttributeValueAt(int index)
            {
                Assert.FailIfNot((index >= 0) && (index < _attributesCount), Assert.Id.Assert00298);
                return _attributes[index];
            }

            /// <summary>
            /// Возвращает значения всех признаков
            /// </summary>
            /// <returns></returns>
            public double[] GetAttributes()
            {
                return _attributes.Clone() as double[];
            }

            /// <summary>
            /// Возвращает класс объекта
            /// </summary>
            public int Class
            {
                get
                {
                    return _class;
                }
            }
        }

        private List<List<ClassificationObject>[]> _rememberedExamples;//точки отката обучающей выборки
        private List<ClassificationObject>[] _examples;//обучающая выборка (в списке набор примеров класса, в массиве - классы)
        private bool _initialized;//флаг проинициализированного классификатора
        private int[] _lastRecognizedClassesMatching;//последний массив соответствий классам        

        /// <summary>
        /// Возвращает/устанавливает последний массив соответствий классам        
        /// </summary>
        protected int[] LastRecognizedClassesMatching
        {
            get
            {
                return _lastRecognizedClassesMatching;
            }
            set
            {
                _lastRecognizedClassesMatching = value;
            }
        }

        /// <summary>
        /// Устанавливает флаг проинициализированного классификатора
        /// </summary>
        protected void SetInitialized(bool newInitialized)
        {
            _initialized = newInitialized;            
        }

        /// <summary>
        /// Возвращает/устанавливает точку отката обучающей выборки
        /// </summary>
        protected List<List<ClassificationObject>[]> RememberedExamples
        {
            get
            {
                return _rememberedExamples;
            }
            set
            {
                _rememberedExamples = value;
            }
        }

        /// <summary>
        /// Возвращает/устанавливает обучающую выборка (в списке набор примеров класса, в массиве - классы)
        /// </summary>
        protected List<ClassificationObject>[] Examples
        {
            get
            {
                return _examples;
            }
            set
            {
                _examples = value;
            }
        }

        /// <summary>
        /// Возвращает флаг проинициализированной классификации
        /// </summary>
        public bool Initialized
        {
            get
            {
                return _initialized;
            }
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        protected AbstractClassificator()
        {
            _initialized = false;
        }        

        /// <summary>
        /// Устанавливает дополнительную информацию для каждого из признаков
        /// </summary>
        /// <param name="infos"></param>
        public abstract void SetInfos(double[] infos);        

        /// <summary>
        /// Откатывает примеры к последней точке отката
        /// </summary>
        public void RollbackExamples()
        {
            if (!_initialized)
                throw new BINotInitializedException(null);
            _examples = _rememberedExamples[_rememberedExamples.Count - 1];
            _rememberedExamples.RemoveAt(_rememberedExamples.Count - 1);
        }

        /// <summary>
        /// Устанавливает точку отката примеров
        /// </summary>
        public void SetExamplesRollbackPoint()
        {
            if (!_initialized)
                throw new BINotInitializedException(null);
            var rememberedExamplesPoint = new List<ClassificationObject>[_examples.Length];
            for (int i = 0; i < _examples.Length; i++)
            {
                List<ClassificationObject> classExamples = _examples[i];
                if (classExamples == null)
                    continue;
                List<ClassificationObject> clonedClassExamples = new List<ClassificationObject>();
                for (int j = 0; j < classExamples.Count; j++)
                {
                    clonedClassExamples.Add(new ClassificationObject(classExamples[j].GetAttributes(), classExamples[j].Class));
                }
                rememberedExamplesPoint[i] = clonedClassExamples;
            }
            _rememberedExamples.Add(rememberedExamplesPoint);
        }

        /// <summary>
        /// Загружает пример
        /// </summary>
        /// <param name="attributes">значения признаков</param>
        /// <param name="class_">класс</param>
        /// <remarks>true - пример загружен, false - нет (потому что уже такой есть)</remarks>
        public bool LoadExample(double[] attributes, int class_)
        {
            if (!_initialized)
                throw new BINotInitializedException(null);
            if (attributes.Length != _attributesCount)
                throw new BIClassificationAttributesCountMismatch(attributes.Length, _attributesCount, null);
            if ((class_ < 0) || (class_ >= _examples.Length))
                throw new BIClassificationIncorrectClass(class_, _examples.Length - 1, null);
            List<ClassificationObject> classExamples = _examples[class_];
            bool haveValuableCoordinates = false;
            for (int i = 0; i < attributes.Length; i++)
            {
                double val = attributes[i];
                if (val != Constants.NullDouble)
                {
                    haveValuableCoordinates = true;
                    break;
                }
            }
            if (!haveValuableCoordinates)
                throw new BIGeometryImpossibleCalculateDistanceException(null);
            if (classExamples == null)
                classExamples = new List<ClassificationObject>();
            ClassificationObject obj = new ClassificationObject(attributes, class_);
            foreach (ClassificationObject haveObj in classExamples)
            {
                if (GeneralMethods.Identical(haveObj.GetAttributes(), obj.GetAttributes()))
                    return false;//уже есть такой объект
            }            
            classExamples.Add(obj);
            _examples[class_] = classExamples;
            return true;
        }
        
        /// <summary>
        /// Классифицирует объект
        /// </summary>
        /// <param name="attributes"></param>
        public void RecognizeClass(double[] attributes)
        {
            if (!_initialized)
                throw new BINotInitializedException(null);
            bool haveValuableCoordinates = false;            
            for (int i = 0; i < attributes.Length; i++)
            {
                double val = attributes[i];
                if (val != Constants.NullDouble)
                {
                    haveValuableCoordinates = true;
                    break;
                }
            }
            if (!haveValuableCoordinates)
                throw new BIGeometryImpossibleCalculateDistanceException(null);
            _lastRecognizedClassesMatching = new int[_examples.Length];
            for (int classIndex = 0; classIndex < _examples.Length; classIndex++)
            {
                if ((_examples[classIndex] == null) || (_examples[classIndex].Count == 0))
                    throw new BIClassificationNoClassExamplesException(classIndex, null);
                _lastRecognizedClassesMatching[classIndex] = 0;
            }            
            OverloadedRecognizeClass(attributes);
        }

        /// <summary>
        /// Классифицирует объект
        /// </summary>
        /// <param name="attributes"></param>
        protected abstract void OverloadedRecognizeClass(double[] attributes);

        /// <summary>
        /// Возвращает решение - класс объекта
        /// </summary>
        /// <returns></returns>
        public int GetSolutionClass()
        {
            if (!_initialized)
                throw new BINotInitializedException(null);
            int maxClass = 0;
            int max = 0;
            for (int i = 0; i < _lastRecognizedClassesMatching.Length; i++)
            {
                if (_lastRecognizedClassesMatching[i] > max)
                {
                    max = _lastRecognizedClassesMatching[i];
                    maxClass = i;
                }
            }
            return maxClass;
        }

        /// <summary>
        /// Возвращает решение - массив уровня соответствий объекта классам (чем больше значение - тем больше соответствует)
        /// </summary>
        /// <returns></returns>
        public int[] GetSolutionEstimatedClasses()
        {
            if (!_initialized)
                throw new BINotInitializedException(null);            
            return _lastRecognizedClassesMatching;
        }
    }
}
